package com.zmn.oms.model.bo.work;

import com.google.common.collect.Lists;
import com.zmn.common.constant.GlobalConsts;
import com.zmn.common.utils.pager.Pages;
import com.zmn.common.utils.string.StringUtil;
import com.zmn.oms.model.entity.work.OrderWorkQuery;
import com.zmn.oms.model.es.ZmnEsFieldType;
import com.zmn.oms.model.es.ZmnToEs;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static java.util.Optional.ofNullable;

/**
 * 类描述：es query builder
 *
 * @author lujia
 * @date 2018/11/29 11:51
 */
public class EsQueryBuilder {

    private final static Logger logger = LoggerFactory.getLogger(EsQueryBuilder.class);

    /**
     * 生成 es query builder
     * @param query 查询条件
     * @return bool query builder
     * @throws IllegalAccessException
     */
    public static BoolQueryBuilder getQuery(Pages query) throws Exception {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();

        Class<ZmnToEs> cls = ZmnToEs.class;

        // 获取所有字段
        List<Field> fieldList = Lists.newArrayList();
        Class queryClass = query.getClass();
        do {
            fieldList.addAll(Lists.newArrayList(queryClass.getDeclaredFields()));
            queryClass = queryClass.getSuperclass();
        } while (!Objects.equals(queryClass.getSimpleName(), "Object"));

        // 过滤出有注解 值不为空的
        fieldList = fieldList.stream()
                .filter(e -> {
                    e.setAccessible(true);
                    Object fieldValue = null;
                    try {
                        fieldValue = e.get(query);
                    } catch (IllegalAccessException ex) {
                        ex.printStackTrace();
                        return false;
                    }
                    return e.isAnnotationPresent(ZmnToEs.class) && fieldValue != null;
                })
                .collect(Collectors.toList());

        for (Field field : fieldList) {

            ZmnToEs annotation = field.getAnnotation(cls);
            if (Objects.equals(ZmnEsFieldType.Or, annotation.type())) {
                QueryBuilder queryBuilder = getShouldQueryBuilder(annotation, field, query);
                builder.filter(queryBuilder);
            } else {
                builder.filter(getQueryBuilderByType(annotation, field, query));
            }
        }
        return builder;
    }

    /**
     * 获取 es page request
     * @param pages 分页对象
     * @return 分页request
     */
    public static PageRequest getPageRequest(Pages pages) {
        if (StringUtil.isBlank(pages.getOrderField())) {
            return PageRequest.of(pages.getPageIndex() - 1, pages.getPageSize());
        }
        String orderField = pages.getOrderField();
        Integer defaultOrder = pages.getDefaultOrder();
        Order order = null;
        if (Objects.equals(Pages.DEFAULT_ORDER_YES, defaultOrder)) {
            order = Order.desc(orderField);
        } else {
            order = Order.asc(orderField);
        }
        return PageRequest.of(pages.getPageIndex() - 1, pages.getPageSize(), Sort.by(order));
    }

    public static PageRequest getPageRequestByProvinceCityName(Pages pages){
        Integer defaultOrder = pages.getDefaultOrder();
        Order provinceOrder = null;
        Order cityOrder = null;
        if (Objects.equals(Pages.DEFAULT_ORDER_YES, defaultOrder)) {
            provinceOrder = Order.desc("provinceName");
            cityOrder = Order.desc("cityName");
        } else {
            provinceOrder = Order.asc("provinceName");
            cityOrder = Order.asc("cityName");
        }
        return PageRequest.of(pages.getPageIndex() - 1, pages.getPageSize(), Sort.by(provinceOrder, cityOrder));
    }

    private static QueryBuilder getShouldQueryBuilder(ZmnToEs annotation, Field field,
            Object query) throws IllegalAccessException {

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        String[] mapArr = annotation.map();
        Object fieldValue = field.get(query);
        if (fieldValue instanceof List) {
            List list = (List)fieldValue;
            for (String map : mapArr) {
                queryBuilder.should(QueryBuilders.termsQuery(map, list.toArray(new Object[list.size()])));
            }
        } else {
            for (String map : mapArr) {
                queryBuilder.should(QueryBuilders.termQuery(map, fieldValue));
            }
        }
        return queryBuilder;
    }

    private static QueryBuilder getQueryBuilderByType(ZmnToEs annotation, Field field,
            Object query)
            throws IllegalAccessException {
        ZmnEsFieldType type = annotation.type();
        switch (type) {
            case Integer:
            case Text:
            case Long:
                return getQueryBuilderByDefualt(annotation, field, query);
            case Match:
                return getQueryBuilderByMatch(annotation, field, query);
            case Like:
                return getQueryBuilderByLike(annotation, field, query);
            case IntegerRange:
                return getQueryBuilderByIntegerRange(annotation, field, query);
            case Nested:
                return getQueryBuilderByNested(annotation, field, query);
            case InNested:
                return getQueryBuilderByInNested(annotation, field, query);
            case Arr:
                return getQueryBuilderByArr(annotation, field, query);
            case Date:
                return getQueryBuilderByDate(annotation, field, query);
            case Boolean:
                return getQueryBuilderByBoolean(annotation, field, query);
            case Exist:
                return getQueryBuilderByExist(annotation, field, query);
            case AmountLevel:
                return getQueryBuilderByAmountLevel(annotation, field, query);
            case NotIn:
                return getNotIn(annotation, field, query);
            case NotInArr:
                return getNotInArr(annotation, field, query);
            case AmountNotExist:
                return getQueryBuilderByAmountNotExist(annotation, field, query);
            default:
                return null;
        }
    }

    private static QueryBuilder getNotIn(ZmnToEs annotation, Field field, Object query) throws IllegalAccessException {
        String[] map = annotation.map();
        Object status = field.get(query);

        return QueryBuilders.boolQuery().mustNot(QueryBuilders.termsQuery(map[0], status));
    }

    private static QueryBuilder getNotInArr(ZmnToEs annotation, Field field, Object query) throws IllegalAccessException {
        String[] map = annotation.map();
        List status = (List) field.get(query);

        return QueryBuilders.boolQuery().mustNot(QueryBuilders.termsQuery(map[0], status.toArray(new Object[status.size()])));
    }
    private static QueryBuilder getQueryBuilderByAmountNotExist(ZmnToEs annotation, Field field, Object query) throws IllegalAccessException {
        Integer flag = (Integer)field.get(query);
        String[] map = annotation.map();
        String fieldName = map[0];

        if (Objects.equals(GlobalConsts.NO, flag)) {
            BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
            queryBuilder.should(QueryBuilders.termQuery(fieldName, 0));
            queryBuilder.should(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery(fieldName)));
            return queryBuilder;
        } else {
            RangeQueryBuilder queryBuilder = QueryBuilders.rangeQuery(fieldName);
            queryBuilder.gte(1);
            return queryBuilder;
        }
    }
    private static QueryBuilder getQueryBuilderByExist(ZmnToEs annotation, Field field, Object query) throws IllegalAccessException {
        Integer flag = (Integer)field.get(query);
        String[] map = annotation.map();
        if (Objects.equals(GlobalConsts.YES, flag)) {
            return QueryBuilders.existsQuery(map[0]);
        } else {
            return QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery(map[0]));
        }
    }

    private static QueryBuilder getQueryBuilderByIntegerRange(ZmnToEs annotation, Field field,
            Object query) throws IllegalAccessException {
        String[] map = annotation.map();
        RangeQueryBuilder queryBuilder = QueryBuilders.rangeQuery(map[0]);
        String dateStr = (String) field.get(query);
        String[] dateArr = dateStr.split(",");

        if (dateArr.length >= 1 && NumberUtils.isCreatable(dateArr[0])) {
            queryBuilder.gte(Integer.parseInt(dateArr[0]));
        }
        if (dateArr.length >= 2 && NumberUtils.isCreatable(dateArr[1])) {
            queryBuilder.lte(Integer.parseInt(dateArr[1]));
        }
        return queryBuilder;
    }

    private static QueryBuilder getQueryBuilderByInNested(ZmnToEs annotation, Field field,
            Object query) throws IllegalAccessException {

        Class<ZmnToEs> cls = ZmnToEs.class;
        String path = annotation.path();
        Object obj = field.get(query);

        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            boolean has = declaredField.isAnnotationPresent(cls);
            Object fieldValue = declaredField.get(obj);
            if (has && ofNullable(fieldValue).isPresent()) {
                ZmnToEs annotation1 = declaredField.getAnnotation(cls);
                if (Objects.equals(ZmnEsFieldType.Or, annotation1.type())) {
                    QueryBuilder queryBuilder = getShouldQueryBuilder(annotation, declaredField,obj);
                    builder.filter(queryBuilder);
                } else {
                    // and 使用 must
                    builder.filter(getQueryBuilderByType(annotation1, declaredField, obj));
                }
            }
        }

        NestedQueryBuilder operateList = QueryBuilders.nestedQuery(path,
                builder,
                ScoreMode.None);

        return operateList;
    }

    private static QueryBuilder getQueryBuilderByAmountLevel(ZmnToEs annotation, Field field,
            Object query) throws IllegalAccessException {
        Integer level = (Integer)field.get(query);
        String[] map = annotation.map();
        int amount = 0;
        switch (level) {
            case 1:
                return QueryBuilders.rangeQuery(map[0]).lt(50 * 100);
            case 2:
                return QueryBuilders.rangeQuery(map[0]).gte(50 * 100);
            case 3:
                return QueryBuilders.rangeQuery(map[0]).gte(100 * 100);
            case 4:
                return QueryBuilders.rangeQuery(map[0]).gte(200 * 100);
            case 5:
                return QueryBuilders.rangeQuery(map[0]).gte(300 * 100);
            case 6:
                return QueryBuilders.rangeQuery(map[0]).gte(400 * 100);
            case 7:
                return QueryBuilders.rangeQuery(map[0]).gte(500 * 100);
            case 8:
                return QueryBuilders.rangeQuery(map[0]).gte(800 * 100);
            case 9:
                return QueryBuilders.rangeQuery(map[0]).gte(1000 * 100);
            case 10:
                return QueryBuilders.rangeQuery(map[0]).gte(2000 * 100);
            default:
                return QueryBuilders.rangeQuery(map[0]).lt(amount);
        }
    }

    private static QueryBuilder getQueryBuilderByBoolean(ZmnToEs annotation, Field field, Object query) throws IllegalAccessException {
        Integer flag = (Integer)field.get(query);
        String[] map = annotation.map();
        if (Objects.equals(GlobalConsts.YES, flag)) {
            return QueryBuilders.termQuery(map[0], true);
        } else {
            return QueryBuilders.termQuery(map[0], false);
        }
    }

    private static QueryBuilder getQueryBuilderByLike(ZmnToEs annotation, Field field, Object query) throws IllegalAccessException {
        String[] map = annotation.map();
        String dateStr = (String) field.get(query);
        return QueryBuilders.wildcardQuery(map[0], String.format("*%s*", dateStr));
    }

    private static QueryBuilder getQueryBuilderByDate(ZmnToEs annotation, Field field,
            Object query) throws IllegalAccessException {
        String[] map = annotation.map();
        RangeQueryBuilder queryBuilder = QueryBuilders.rangeQuery(map[0]);
        String dateStr = (String) field.get(query);
        String[] dateArr = dateStr.split(",", -1);
        if (dateArr.length > 1) {
            if (StringUtil.isNotBlank(dateArr[0])) {
                queryBuilder.gte(dateArr[0]);
            }
            if (StringUtil.isNotBlank(dateArr[1])) {
                queryBuilder.lte(dateArr[1]);
            }
        } else {
            logger.error("{} 日期入参格式不对 startTime,endTime", map);
        }
        return queryBuilder;
    }

    private static QueryBuilder getQueryBuilderByArr(ZmnToEs annotation, Field field,
            Object query) throws IllegalAccessException {
        String[] map = annotation.map();
        List orderStatus = (List) field.get(query);
        return QueryBuilders.termsQuery(map[0], orderStatus.toArray(new Object[orderStatus.size()]));
    }

    private static QueryBuilder getQueryBuilderByNested(ZmnToEs annotation, Field field,
            Object query) throws IllegalAccessException {
        String path = annotation.path();
        ZmnEsFieldType type = annotation.pathType();

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        switch (type) {
            case Integer:
            case Text:
                queryBuilder.filter(getQueryBuilderByDefualt(annotation, field, query));
                break;
            case Arr:
                queryBuilder.filter(getQueryBuilderByArr(annotation, field, query));
                break;
            case Match:
                queryBuilder.filter(getQueryBuilderByMatch(annotation, field, query));
                break;
            case Boolean:
                queryBuilder.filter(getQueryBuilderByBoolean(annotation, field, query));
                break;
            default:
                logger.error("#es#build not found type build");
        }

        NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(path,
                queryBuilder,
                ScoreMode.None);

        return nestedQueryBuilder;
    }

    private static QueryBuilder getQueryBuilderByDefualt(ZmnToEs annotation, Field field,
            Object query) throws IllegalAccessException {
        String[] map = annotation.map();
        return QueryBuilders.termQuery(map[0], field.get(query));
    }

    private static QueryBuilder getQueryBuilderByMatch(ZmnToEs annotation, Field field,
            Object query) throws IllegalAccessException {
        String[] map = annotation.map();
        return QueryBuilders.matchQuery(map[0], field.get(query));
    }

    /**
     * 获取待解析的属性字段集合
     * @param query
     * @return
     */
    private List<Field> getSupportFieldList(OrderWorkQuery query) {

        Class queryClass = query.getClass();
        List<Field> fieldList = Lists.newArrayList();

        // 获取所有类属性字段 包含父类
        do {
            fieldList.addAll(Lists.newArrayList(queryClass.getDeclaredFields()));
            queryClass = queryClass.getSuperclass();
        } while (!Objects.equals(queryClass.getSimpleName(), "Object"));

        // 过滤出 需要解析 字段值不为空的
        fieldList = fieldList.stream()
                .filter(e -> {
                    e.setAccessible(true);
                    Object fieldValue = null;
                    try {
                        fieldValue = e.get(query);
                    } catch (IllegalAccessException ex) {
                        logger.error(ex.getMessage(), e);
                        return false;
                    }
                    return e.isAnnotationPresent(ZmnToEs.class) && fieldValue != null;
                })
                .collect(Collectors.toList());

        return fieldList;
    }

}
